/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.eagleeyes.hoverbuttons;

import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.util.HashSet;
import java.util.LinkedList;

/**
 * An EEMouseMotionListener is a generic event listener for Eagle Eyes.  Because Eagle Eyes has no form of
 * clicking like a traditional mouse, all input must be done with mouse motion.  <br/>
 * 
 * Focusable items are the "clickable" areas of the screen.  A focus is performed by hovering over the area
 * for a certain amount of time, which can be set through set through the setFocusTimeout method.  By
 * default this is set to DEFAULT_TIMEOUT milliseconds.<br/>
 * 
 * Focusable items may be added to the EEMouseMotionListener through the add(EEFocusable) method.
 *
 * @version 0.5
 * @author Jesse Bahr
 */
public class EEMouseMotionListener extends Thread implements MouseMotionListener{
  /**
   * The default number of milliseconds required to focus an EEFocusable.
   */
  public static final int DEFAULT_TIMEOUT = 1100;
  private static final int THREAD_TIMEOUT = 20;
  
  private int focusTimeout;
  private long lastTime;
  private LinkedList<EEFocusable> focusables;
  private HashSet<EEHighlightedFocusable> highlightedFocusables;
  private HashSet<EEFocusable> focusedFocusables;
      
  /**
   * create an EEMouseMotionListener with the default timeout for focusing.
   */
  public EEMouseMotionListener(){
    focusTimeout = DEFAULT_TIMEOUT;
    focusables = new LinkedList<EEFocusable>();
    highlightedFocusables = new HashSet<EEHighlightedFocusable>();
    focusedFocusables = new HashSet<EEFocusable>();
    lastTime = System.currentTimeMillis();
    start();
  }
  
  /**
   * create an EEMouseMotionListener with the given timeout for focusing.
   * @param focusTimeout the timeout required to focus an EEFocusable in milliseconds.
   */
  public EEMouseMotionListener(int focusTimeout){
    this.focusTimeout = focusTimeout;
    focusables = new LinkedList<EEFocusable>();
  }
  

  /**
   * respond to a mouse dragged event.  Since there
   * @param e
   */
  public void mouseDragged(MouseEvent e) {
  }
  
  @Override public void run(){
    long currTime;
    int elapsed;
    LinkedList<EEHighlightedFocusable> toRemove = new LinkedList<EEHighlightedFocusable>();
    LinkedList<EEFocusable> toFocus = new LinkedList<EEFocusable>();
    
    while(true){
      synchronized(highlightedFocusables){
        currTime = System.currentTimeMillis();
        elapsed = (int) (currTime - lastTime);
        lastTime = currTime;
    
        toRemove.clear();
        toFocus.clear();
        for (EEHighlightedFocusable hfoc : highlightedFocusables){
          hfoc.setElapsed(hfoc.getElapsed() + elapsed);

          if (hfoc.getElapsed() > focusTimeout){
            toFocus.add(hfoc.getFocusable());
            hfoc.getFocusable().getListener().focus();
            toRemove.add(hfoc);
          }
          else{
            float elapsedPerc = ((float) hfoc.getElapsed()) / ((float) focusTimeout);
            hfoc.getFocusable().getListener().highlight(elapsedPerc);
          }
        }
        highlightedFocusables.removeAll(toRemove);
      }
      
      synchronized(focusedFocusables){
        focusedFocusables.addAll(toFocus);
      }
      
      try{
        Thread.sleep(THREAD_TIMEOUT);
      }catch (InterruptedException e){}
    }
  }

  /**
   * detect mouse moves, invoking the appropriate events.
   * @param e
   */
  public void mouseMoved(MouseEvent e) {
    
    /* ****** remove if no longer hovered */
    synchronized(highlightedFocusables){
      LinkedList<EEHighlightedFocusable> toRemove = new LinkedList<EEHighlightedFocusable>();
      for (EEHighlightedFocusable hfoc : highlightedFocusables){
        EEFocusable foc = hfoc.getFocusable();

        if (!foc.contains(e.getX(), e.getY())){
          hfoc.getFocusable().getListener().unhighlight();
          toRemove.add(hfoc);
        }
      }
      highlightedFocusables.removeAll(toRemove);
    
      /* **************** add any hovered ones unless they've been focused or they're already hovered */
      for (EEFocusable foc : focusables){
        if (foc.contains(e.getX(), e.getY()) 
            && !highlightedFocusables.contains(foc) && !focusedFocusables.contains(foc))
          highlightedFocusables.add(new EEHighlightedFocusable(foc));
      }
    }
    
    synchronized(focusedFocusables){
      /* ************* remove former focused ones that aren't hovered any more */
      LinkedList<EEFocusable> focusablesToRemove = new LinkedList<EEFocusable>();
      for (EEFocusable foc : focusedFocusables){
        if (!foc.contains(e.getX(), e.getY()))
          focusablesToRemove.add(foc);
      }
      focusedFocusables.removeAll(focusablesToRemove);
    }
    
  }
  
  public void removeAll(){
    focusables.clear();
    highlightedFocusables.clear();
    focusedFocusables.clear();
  }

  public int getFocusTimeout() {
    return focusTimeout;
  }

  public void setFocusTimeout(int focusTimeout) {
    this.focusTimeout = focusTimeout;
  }
  
  public void add(EEFocusable f){
    focusables.add(f);
  }
  
  public void remove(EEFocusable f){
    focusables.remove(f);
  }

  
}
